﻿using System;
using System.Runtime.InteropServices;
using HandyControl.Tools.Interop;

namespace HandyControl.Data;

public abstract class HwndWrapper : DisposableObject
{
    private IntPtr _handle;

    private bool _isHandleCreationAllowed = true;

    private ushort _wndClassAtom;

    private Delegate _wndProc;

    protected ushort WindowClassAtom
    {
        get
        {
            if (_wndClassAtom == 0) _wndClassAtom = CreateWindowClassCore();
            return _wndClassAtom;
        }
    }

    public IntPtr Handle
    {
        get
        {
            EnsureHandle();
            return _handle;
        }
    }

    protected virtual bool IsWindowSubclassed => false;

    protected virtual ushort CreateWindowClassCore() => RegisterClass(Guid.NewGuid().ToString());

    protected virtual void DestroyWindowClassCore()
    {
        if (_wndClassAtom != 0)
        {
            var moduleHandle = InteropMethods.GetModuleHandle(null);
            InteropMethods.UnregisterClass(new IntPtr(_wndClassAtom), moduleHandle);
            _wndClassAtom = 0;
        }
    }

    protected ushort RegisterClass(string className)
    {
        var wndClass = default(InteropValues.WNDCLASS);
        wndClass.cbClsExtra = 0;
        wndClass.cbWndExtra = 0;
        wndClass.hbrBackground = IntPtr.Zero;
        wndClass.hCursor = IntPtr.Zero;
        wndClass.hIcon = IntPtr.Zero;
        wndClass.lpfnWndProc = _wndProc = new InteropValues.WndProc(WndProc);
        wndClass.lpszClassName = className;
        wndClass.lpszMenuName = null;
        wndClass.style = 0u;
        return InteropMethods.RegisterClass(ref wndClass);
    }

    private void SubclassWndProc()
    {
        _wndProc = new InteropValues.WndProc(WndProc);
        InteropMethods.SetWindowLong(_handle, -4, Marshal.GetFunctionPointerForDelegate(_wndProc));
    }

    protected abstract IntPtr CreateWindowCore();

    protected virtual void DestroyWindowCore()
    {
        if (_handle != IntPtr.Zero)
        {
            InteropMethods.DestroyWindow(_handle);
            _handle = IntPtr.Zero;
        }
    }

    protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) =>
        InteropMethods.DefWindowProc(hwnd, msg, wParam, lParam);

    public void EnsureHandle()
    {
        if (_handle == IntPtr.Zero)
        {
            if (!_isHandleCreationAllowed) return;
            _isHandleCreationAllowed = false;
            _handle = CreateWindowCore();
            if (IsWindowSubclassed) SubclassWndProc();
        }
    }

    protected override void DisposeNativeResources()
    {
        _isHandleCreationAllowed = false;
        DestroyWindowCore();
        DestroyWindowClassCore();
    }
}
